﻿using System;
using System.Collections.Generic;
using GeoAPI.Geometries;
using NetTopologySuite.Algorithm;
using NetTopologySuite.Geometries;
using NetTopologySuite.Geometries.Utilities;

namespace NetTopologySuite.Operation.Distance
{
	public class DistanceOp
	{
		public static double Distance(IGeometry g0, IGeometry g1)
		{
			return new DistanceOp(g0, g1).Distance();
		}

		public static bool IsWithinDistance(IGeometry g0, IGeometry g1, double distance)
		{
			return new DistanceOp(g0, g1, distance).Distance() <= distance;
		}

		public static Coordinate[] NearestPoints(IGeometry g0, IGeometry g1)
		{
			return new DistanceOp(g0, g1).NearestPoints();
		}

		[Obsolete("Renamed to NearestPoints")]
		public static Coordinate[] ClosestPoints(IGeometry g0, IGeometry g1)
		{
			return DistanceOp.NearestPoints(g0, g1);
		}

		public DistanceOp(IGeometry g0, IGeometry g1) : this(g0, g1, 0.0)
		{
		}

		public DistanceOp(IGeometry g0, IGeometry g1, double terminateDistance)
		{
			this._geom = new IGeometry[]
			{
				g0,
				g1
			};
			this._terminateDistance = terminateDistance;
		}

		public double Distance()
		{
			if (this._geom[0] == null || this._geom[1] == null)
			{
				throw new ApplicationException("null geometries are not supported");
			}
			if (this._geom[0].IsEmpty || this._geom[1].IsEmpty)
			{
				return 0.0;
			}
			this.ComputeMinDistance();
			return this._minDistance;
		}

		[Obsolete("Renamed to NearestPoints")]
		public Coordinate[] ClosestPoints()
		{
			return this.NearestPoints();
		}

		[Obsolete("Renamed to NearestLocations")]
		public GeometryLocation[] ClosestLocations()
		{
			return this.NearestLocations();
		}

		public Coordinate[] NearestPoints()
		{
			this.ComputeMinDistance();
			return new Coordinate[]
			{
				this._minDistanceLocation[0].Coordinate,
				this._minDistanceLocation[1].Coordinate
			};
		}

		public GeometryLocation[] NearestLocations()
		{
			this.ComputeMinDistance();
			return this._minDistanceLocation;
		}

		private void UpdateMinDistance(double dist)
		{
			if (dist < this._minDistance)
			{
				this._minDistance = dist;
			}
		}

		private void UpdateMinDistance(GeometryLocation[] locGeom, bool flip)
		{
			if (locGeom[0] == null)
			{
				return;
			}
			if (flip)
			{
				this._minDistanceLocation[0] = locGeom[1];
				this._minDistanceLocation[1] = locGeom[0];
				return;
			}
			this._minDistanceLocation[0] = locGeom[0];
			this._minDistanceLocation[1] = locGeom[1];
		}

		private void ComputeMinDistance()
		{
			if (this._minDistanceLocation != null)
			{
				return;
			}
			this._minDistanceLocation = new GeometryLocation[2];
			this.ComputeContainmentDistance();
			if (this._minDistance <= this._terminateDistance)
			{
				return;
			}
			this.ComputeFacetDistance();
		}

		private void ComputeContainmentDistance()
		{
			GeometryLocation[] locPtPoly = new GeometryLocation[2];
			this.ComputeContainmentDistance(0, locPtPoly);
			if (this._minDistance <= this._terminateDistance)
			{
				return;
			}
			this.ComputeContainmentDistance(1, locPtPoly);
		}

		private void ComputeContainmentDistance(int polyGeomIndex, GeometryLocation[] locPtPoly)
		{
			int num = 1 - polyGeomIndex;
			IList<IGeometry> polygons = PolygonExtracter.GetPolygons(this._geom[polyGeomIndex]);
			if (polygons.Count > 0)
			{
				IList<GeometryLocation> locations = ConnectedElementLocationFilter.GetLocations(this._geom[num]);
				this.ComputeContainmentDistance(locations, polygons, locPtPoly);
				if (this._minDistance <= this._terminateDistance)
				{
					this._minDistanceLocation[num] = locPtPoly[0];
					this._minDistanceLocation[polyGeomIndex] = locPtPoly[1];
				}
			}
		}

		private void ComputeContainmentDistance(IList<GeometryLocation> locs, ICollection<IGeometry> polys, GeometryLocation[] locPtPoly)
		{
			for (int i = 0; i < locs.Count; i++)
			{
				GeometryLocation ptLoc = locs[i];
				foreach (IGeometry geometry in polys)
				{
					IPolygon poly = (IPolygon)geometry;
					this.ComputeContainmentDistance(ptLoc, poly, locPtPoly);
					if (this._minDistance <= this._terminateDistance)
					{
						return;
					}
				}
			}
		}

		private void ComputeContainmentDistance(GeometryLocation ptLoc, IPolygon poly, GeometryLocation[] locPtPoly)
		{
			Coordinate coordinate = ptLoc.Coordinate;
			if (Location.Exterior != this._ptLocator.Locate(coordinate, poly))
			{
				this._minDistance = 0.0;
				locPtPoly[0] = ptLoc;
				locPtPoly[1] = new GeometryLocation(poly, coordinate);
			}
		}

		private void ComputeFacetDistance()
		{
			GeometryLocation[] array = new GeometryLocation[2];
			ICollection<IGeometry> lines = LinearComponentExtracter.GetLines(this._geom[0]);
			ICollection<IGeometry> lines2 = LinearComponentExtracter.GetLines(this._geom[1]);
			ICollection<IGeometry> points = PointExtracter.GetPoints(this._geom[0]);
			ICollection<IGeometry> points2 = PointExtracter.GetPoints(this._geom[1]);
			this.ComputeMinDistanceLines(lines, lines2, array);
			this.UpdateMinDistance(array, false);
			if (this._minDistance <= this._terminateDistance)
			{
				return;
			}
			array[0] = null;
			array[1] = null;
			this.ComputeMinDistanceLinesPoints(lines, points2, array);
			this.UpdateMinDistance(array, false);
			if (this._minDistance <= this._terminateDistance)
			{
				return;
			}
			array[0] = null;
			array[1] = null;
			this.ComputeMinDistanceLinesPoints(lines2, points, array);
			this.UpdateMinDistance(array, true);
			if (this._minDistance <= this._terminateDistance)
			{
				return;
			}
			array[0] = null;
			array[1] = null;
			this.ComputeMinDistancePoints(points, points2, array);
			this.UpdateMinDistance(array, false);
		}

		private void ComputeMinDistanceLines(IEnumerable<IGeometry> lines0, ICollection<IGeometry> lines1, GeometryLocation[] locGeom)
		{
			foreach (IGeometry geometry in lines0)
			{
				ILineString line = (ILineString)geometry;
				foreach (IGeometry geometry2 in lines1)
				{
					ILineString line2 = (ILineString)geometry2;
					this.ComputeMinDistance(line, line2, locGeom);
					if (this._minDistance <= this._terminateDistance)
					{
						return;
					}
				}
			}
		}

		private void ComputeMinDistancePoints(IEnumerable<IGeometry> points0, ICollection<IGeometry> points1, GeometryLocation[] locGeom)
		{
			foreach (IGeometry geometry in points0)
			{
				IPoint point = (IPoint)geometry;
				foreach (IGeometry geometry2 in points1)
				{
					IPoint point2 = (IPoint)geometry2;
					double num = point.Coordinate.Distance(point2.Coordinate);
					if (num < this._minDistance)
					{
						this._minDistance = num;
						locGeom[0] = new GeometryLocation(point, 0, point.Coordinate);
						locGeom[1] = new GeometryLocation(point2, 0, point2.Coordinate);
					}
					if (this._minDistance <= this._terminateDistance)
					{
						return;
					}
				}
			}
		}

		private void ComputeMinDistanceLinesPoints(IEnumerable<IGeometry> lines, ICollection<IGeometry> points, GeometryLocation[] locGeom)
		{
			foreach (IGeometry geometry in lines)
			{
				ILineString line = (ILineString)geometry;
				foreach (IGeometry geometry2 in points)
				{
					IPoint pt = (IPoint)geometry2;
					this.ComputeMinDistance(line, pt, locGeom);
					if (this._minDistance <= this._terminateDistance)
					{
						return;
					}
				}
			}
		}

		private void ComputeMinDistance(ILineString line0, ILineString line1, GeometryLocation[] locGeom)
		{
			if (line0.EnvelopeInternal.Distance(line1.EnvelopeInternal) > this._minDistance)
			{
				return;
			}
			Coordinate[] coordinates = line0.Coordinates;
			Coordinate[] coordinates2 = line1.Coordinates;
			for (int i = 0; i < coordinates.Length - 1; i++)
			{
				for (int j = 0; j < coordinates2.Length - 1; j++)
				{
					double num = CGAlgorithms.DistanceLineLine(coordinates[i], coordinates[i + 1], coordinates2[j], coordinates2[j + 1]);
					if (num < this._minDistance)
					{
						this._minDistance = num;
						LineSegment lineSegment = new LineSegment(coordinates[i], coordinates[i + 1]);
						LineSegment line2 = new LineSegment(coordinates2[j], coordinates2[j + 1]);
						Coordinate[] array = lineSegment.ClosestPoints(line2);
						locGeom[0] = new GeometryLocation(line0, i, array[0]);
						locGeom[1] = new GeometryLocation(line1, j, array[1]);
					}
					if (this._minDistance <= this._terminateDistance)
					{
						return;
					}
				}
			}
		}

		private void ComputeMinDistance(ILineString line, IPoint pt, GeometryLocation[] locGeom)
		{
			if (line.EnvelopeInternal.Distance(pt.EnvelopeInternal) > this._minDistance)
			{
				return;
			}
			Coordinate[] coordinates = line.Coordinates;
			Coordinate coordinate = pt.Coordinate;
			for (int i = 0; i < coordinates.Length - 1; i++)
			{
				double num = CGAlgorithms.DistancePointLine(coordinate, coordinates[i], coordinates[i + 1]);
				if (num < this._minDistance)
				{
					this._minDistance = num;
					Coordinate pt2 = new LineSegment(coordinates[i], coordinates[i + 1]).ClosestPoint(coordinate);
					locGeom[0] = new GeometryLocation(line, i, pt2);
					locGeom[1] = new GeometryLocation(pt, 0, coordinate);
				}
				if (this._minDistance <= this._terminateDistance)
				{
					return;
				}
			}
		}

		private readonly PointLocator _ptLocator = new PointLocator();

		private readonly IGeometry[] _geom;

		private GeometryLocation[] _minDistanceLocation;

		private double _minDistance = double.MaxValue;

		private readonly double _terminateDistance;
	}
}
